Keep idle SSH tunnel sessions alive on dedicated clusters - #6382
Open
anton-107 wants to merge 1 commit into
Open
Keep idle SSH tunnel sessions alive on dedicated clusters#6382anton-107 wants to merge 1 commit into
anton-107 wants to merge 1 commit into
Conversation
The websocket keepalive ping never reaches a dedicated cluster: the driver proxy answers control frames itself, so the tunnel leg past it stays idle and is reaped after ~8m20s. Generate real SSH payload instead, from both ends of the tunnel — ServerAliveInterval on the ssh client the CLI spawns and in the host config it writes, and ClientAliveInterval in the sshd config the tunnel's server writes. Co-authored-by: Isaac <no-reply@databricks.com>
anton-107
changed the base branch from
main
to
implement-deco-28186-spec-in-comment-do-not-stop-6
August 25, 2026 18:32
Contributor
Waiting for approvalBased on git history, these people are best suited to review:
Eligible reviewers: Suggestions based on git history. See OWNERS for ownership rules. |
Collaborator
Integration test reportCommit: 4fe97ea
Top 3 slowest tests (at least 2 minutes):
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Changes
#6358 fixes idle-session drops on serverless and does nothing for a dedicated cluster. Its websocket ping is a control frame: the driver proxy terminates it itself, so it never becomes payload on the leg past it, and that leg is reaped after ~8m20s of carrying nothing. Zero pongs came back in any run on either compute type — the frame does not traverse. Serverless sits behind a different reaper that the ping does reset, which is why it fixes serverless and only serverless.
The fix is to generate traffic the tunnel forwards end to end. An SSH keepalive reply is a real SSH packet, so the proxy loops carry it as a websocket data frame across every hop — which is why setting
ServerAliveIntervalby hand is a total workaround. The CLI now asks for it itself, from both ends:ServerAliveInterval 30inbuildSSHArgs(theconnectpath) andGenerateHostConfig(thesetupand--idepaths). Needs nothing from the compute, so it also fixes a cluster already running a server binary from an older CLI.ClientAliveInterval 30in the sshd config the tunnel's server writes. Covers clients the CLI never configures — a hand-writtenProxyCommandblock, or an IDE supplying its own ssh options.Either half alone is sufficient (measured below). Both are in because they close different gaps and neither subsumes the other. The websocket ping stays: it is what fixes serverless, and it keeps the client↔control-plane hop warm regardless of the user's ssh config.
One behaviour change, deliberately. These options bring in
ServerAliveCountMax/ClientAliveCountMax(OpenSSH default 3), so a tunnel that stops responding is now torn down after ~90s with ssh's own "server not responding" message instead of hanging. That is the trade we want — today a reaped idle session is a silent black hole, which is the original report — and 90s is well clear of the longest legitimate pause on a healthy tunnel, the up to 30s a handover can hold the sending loop (proxyHandoverInitTimeout).sshdConfigContentisprepareSSHDConfig's config string moved verbatim into a function plus the one new line, so the content is testable without mocking three secret reads. #6358's changelog fragment is updated rather than joined by a second, contradicting one.An out-of-band keepalive frame of our own was rejected:
runReceivingLoopwrites every binary message straight into the SSH byte stream, so it needs a framing change on both ends and only helps once the uploaded server binary is new enough.Resolves DECO-28186 for dedicated clusters; #6358 resolves it for serverless.
Why
Dedicated compute is what the README requires for remote development in an IDE, so the compute type most affected was the one #6358 left broken. Shipping only the websocket ping would close the ticket with the reported failure still in place for those users, and add a log line that makes an idle session look healthy while it is dying.
Tests
Three unit tests, one per site the directive has to appear in, each also bounding the interval against the reap window and against the handover pause. All six mutants killed: dropping the option from each of the three sites, setting either constant to
0, and pushing the interval past the reap window../task test-exp-ssh,./task lint(0 issues, all three modules),./task fmt, and the whitespace, deadcode and changelog checks are all clean; the four touched packages are green under-race.End-to-end (dogfood, dedicated clusters)
One dedicated single-node cluster per build — two tunnels on one cluster collide on port 7772 and take each other down. DBR 17.3 LTS, m5d.xlarge, SINGLE_USER; distinct pinned version per build, since
uploadReleasesskips the upload when the versioned workspace path already exists. Every session ran on a real PTY, was left completely untyped for the whole window, then sent exactly one command. Idle is measured remote-clock to remote-clock.ServerAliveIntervalClientAliveIntervaldc-basedc-fixdc-sshd-onlydc-fix-longdc-basereproduces the bug on Keep idle SSH tunnel sessions alive with a websocket ping #6358's own head, as reported: alive-looking for the whole window, killed by the first keystroke. The server lost its half at18:04:46— 8m20s after the session went quiet — withwebsocket: close 1006 (abnormal closure): unexpected EOF. The client had sent 30 pings with zero failures by then; its next two failed, after the server was already gone. Endedclose 4000: Handler crashed: ...ClosedStreamException,exit status 255.dc-sshd-onlyisolates the server half: same build, but ssh invoked by hand with-o ServerAliveInterval=0, so sshd drives the only SSH-level keepalive. A command-line-obeats any config file in OpenSSH, andClientAliveInterval 30was read back off the cluster's generatedsshd_config. The complementary control for the client half — old server binary, client-side option only — passed earlier on the same setup.dc-fix-longis past two reap windows, so the PASS is a real fix rather than a delayed drop.Full measurements and log excerpts are on DECO-28186. The test clusters have been terminated.
This PR and its description were written by Isaac.